home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 007 / tsr25src.arc / RELEASE.PAS < prev    next >
Pascal/Delphi Source File  |  1987-06-02  |  32KB  |  915 lines

  1. {**************************************************************************
  2. *   Releases memory above the last MARK call made.                        *
  3. *   Copyright (c) 1986 Kim Kokkonen, TurboPower Software.                 *
  4. *   Released to the public domain for personal, non-commercial use only.  *
  5. ***************************************************************************
  6. *   Version 1.0 2/8/86                                                    *
  7. *     original public release                                             *
  8. *     (thanks to Neil Rubenking for an outline of the method used)        *
  9. *   Version 1.1 2/11/86                                                   *
  10. *     fixed problem with processes which deallocate their environment     *
  11. *   Version 1.2 2/13/86                                                   *
  12. *     fixed another problem with processes which deallocate environment   *
  13. *   Version 1.3 2/15/86                                                   *
  14. *     add support for "named" marks                                       *
  15. *   Version 1.4 2/23/86                                                   *
  16. *     add support for releasing programs which use Expanded Memory        *
  17. *   Version 1.5 2/28/86                                                   *
  18. *     add more bulletproof method of finding first allocation block       *
  19. *   Version 1.6 3/20/86                                                   *
  20. *     restore all FF interrupts.                                          *
  21. *     restore the termination address to the local process                *
  22. *     reduce number of EMS blocks to 32.                                  *
  23. *     fix bug in number of EMS handles in EMS release step                *
  24. *     restore an undocumented address in the PSP which allows RELEASE of  *
  25. *       a COMMAND shell (emulates the EXIT command)                       *
  26. *   Version 1.7 (date not recorded)                                       *
  27. *     add "protected" marks                                               *
  28. *   Version 1.8 4/21/86                                                   *
  29. *     fix problem when mark is installed as 'MARK '                       *
  30. *   Version 1.9 5/22/86                                                   *
  31. *     release the environment of MARK when it is not contiguous with      *
  32. *       the MARK itself                                                   *
  33. *     capture RELEASE calls from within batch files and don't release the *
  34. *       batch control block                                               *
  35. *     fiddle with different methods of restoring interrupt vectors in     *
  36. *       an attempt to successfully remove DoubleDos. No success, not      *
  37. *       implemented. Note, after more effort: DDos apparently             *
  38. *       reprograms the 8259 as well as patching the operating system      *
  39. *   Version 2.0 6/17/86                                                   *
  40. *     support "file" marks placed by the new program FMARK                *
  41. *   Version 2.1 7/18/86                                                   *
  42. *     fix bug in restoring "parent" address in RELEASE PSP                *
  43. *   Version 2.2 3/3/87                                                    *
  44. *     add option to revector 8259 interrupt controller                    *
  45. *       (thanks to Steve Glynn for this code)                             *
  46. *     add option to leave mark in place when RELEASE is run               *
  47. *     restore save areas for EGA and interapplication communications      *
  48. *   Version 2.3 5/2/87                                                    *
  49. *     update watch area, if any, when releasing                           *
  50. *   Version 2.4 5/17/87                                                   *
  51. *     avoids use of EMS call $4B, which doesn't work in many EMS          *
  52. *       implementations                                                   *
  53. *     adds switch to ignore EMS altogether                                *
  54. *   Version 2.5 6/2/87                                                    *
  55. *     check version number of mark to avoid incompatibilities             *
  56. *                                                                         *
  57. ***************************************************************************
  58. *   telephone: 408-438-8608, CompuServe: 72457,2131.                      *
  59. *   requires Turbo version 3 to compile.                                  *
  60. *   Compile with mAx dynamic memory = FFFF.                               *
  61. ***************************************************************************}
  62.  
  63. {$P128}
  64. {$C-}
  65.  
  66. program ReleaseTSR;
  67.   {-Release system memory above the last mark call}
  68.   {-Release expanded memory blocks allocated since the last mark call}
  69.  
  70. const
  71.   Version = '2.5';
  72.   ProtectChar = '!';          {Marks whose name begins with this will be
  73.                               released ONLY if an exact name match occurs}
  74.   MaxBlocks = 128;            {Max number of DOS allocation blocks supported}
  75.   MaxHandles = 32;            {Max number of EMS allocation blocks supported}
  76.   EMSinterrupt = $67;         {The vector used by the expanded memory manager}
  77.  
  78.   MarkID = 'M2.5 PARAMETER BLOCK FOLLOWS'; {Marking string for TSR MARK}
  79.   FmarkID = 'FM2.5 TSR';      {Marking string for TSR file mark}
  80.  
  81.   {Offsets into resident copy of MARK.COM for data storage}
  82.   MarkOffset = $103;          {Where markID is found in MARK TSR}
  83.   FmarkOffset = $60;          {Where fmarkID is found in FMARK TSR}
  84.   VectorOffset = $120;        {Where vector table is stored}
  85.   EGAsavOffset = $520;        {Where the EGA save save is stored}
  86.   IntComOffset = $528;        {Where the interapps comm area is stored}
  87.   EMScntOffset = $538;        {Where count of EMS active pages is stored}
  88.   EMSmapOffset = $53A;        {Where the page map is stored}
  89.  
  90.   WatchID = 'TSR WATCHER';    {Marking string for WATCH}
  91.  
  92.   {Offsets into resident copy of WATCH.COM for data storage}
  93.   WatchOffset = $81;
  94.   NextChange = $104;
  95.   ChangeVectors = $220;
  96.   OrigVectors = $620;
  97.   CurrVectors = $A20;
  98.   MaxChanges = 128;           {Maximum number of vector changes stored in WATCH}
  99.  
  100. type
  101.   Registers =
  102.   record
  103.     case Integer of
  104.       1 : (ax, bx, cx, dx, bp, si, di, ds, es, flags : Integer);
  105.       2 : (al, ah, bl, bh, cl, ch, dl, dh : Byte);
  106.   end;
  107.  
  108.   HandlePageRecord =
  109.   record
  110.     handle : Integer;
  111.     numpages : Integer;
  112.   end;
  113.  
  114.   PageArray = array[1..MaxHandles] of HandlePageRecord;
  115.   PageArrayPtr = ^PageArray;
  116.  
  117.   Block =
  118.   record                      {Store info about each memory block}
  119.     mcb : Integer;
  120.     psp : Integer;
  121.     releaseIt : Boolean;
  122.   end;
  123.  
  124.   BlockType = 0..MaxBlocks;
  125.   BlockArray = array[BlockType] of Block;
  126.  
  127.   HexString = string[4];
  128.   Pathname = string[64];
  129.   AllStrings = string[255];
  130.  
  131. var
  132.   Blocks : BlockArray;
  133.   watchBlock, bottomBlock, blockNum : BlockType;
  134.  
  135.   markName : AllStrings;
  136.   Regs : Registers;
  137.  
  138.   FilMarkHandles, ReturnCode, StartMCB, StoredHandles, EMShandles : Integer;
  139.   UseWatch, Debug, Revector8259, DealWithEMS,
  140.   KeepMark, MemMark, FilMark, Junk : Boolean;
  141.  
  142.   FilMarkPageMap, Map, StoredMap : PageArrayPtr;
  143.   TrappedBytes : Real;
  144.  
  145.   {Save areas read in from file mark}
  146.   Vectors : array[0..1023] of Byte;
  147.   EGAsavTable : array[0..7] of Byte;
  148.   IntComTable : array[0..15] of Byte;
  149.  
  150.   procedure Abort(msg : AllStrings);
  151.     {-Halt in case of error}
  152.   begin
  153.     WriteLn(msg);
  154.     Halt(1);
  155.   end {Abort} ;
  156.  
  157.   procedure Halt(ReturnCode : Integer);
  158.     {-Replace Turbo halt with one that doesn't restore any interrupts}
  159.   begin
  160.     Close(Output);
  161.     with Regs do begin
  162.       ah := $4C;
  163.       al := Lo(ReturnCode);
  164.       MsDos(Regs);
  165.     end;
  166.   end {Halt} ;
  167.  
  168.   procedure FindTheBlocks;
  169.     {-Scan memory for the allocated memory blocks}
  170.   const
  171.     MidBlockID = $4D;         {Byte DOS uses to identify part of MCB chain}
  172.     EndBlockID = $5A;         {Byte DOS uses to identify last block of MCB chain}
  173.   var
  174.     mcbSeg : Integer;         {Segment address of current MCB}
  175.     nextSeg : Integer;        {Computed segment address for the next MCB}
  176.     gotFirst : Boolean;       {True after first MCB is found}
  177.     gotLast : Boolean;        {True after last MCB is found}
  178.     idbyte : Byte;            {Byte that DOS uses to identify an MCB}
  179.  
  180.     function GetStartMCB : Integer;
  181.       {-Return the first MCB segment}
  182.     begin
  183.       Regs.ah := $52;
  184.       MsDos(Regs);
  185.       GetStartMCB := MemW[Regs.es:(Regs.bx-2)];
  186.     end {Getstartmcb} ;
  187.  
  188.     procedure StoreTheBlock(var mcbSeg, nextSeg : Integer;
  189.                             var gotFirst, gotLast : Boolean);
  190.       {-Store information regarding the memory block}
  191.     var
  192.       nextID : Byte;
  193.       pspAdd : Integer;       {Segment address of the current PSP}
  194.       mcbLen : Integer;       {Size of the current memory block in paragraphs}
  195.  
  196.     begin
  197.  
  198.       mcbLen := MemW[mcbSeg:3]; {Size of the MCB in paragraphs}
  199.       nextSeg := Succ(mcbSeg+mcbLen); {Where the next MCB should be}
  200.       pspAdd := MemW[mcbSeg:1]; {Address of program segment prefix for MCB}
  201.       nextID := Mem[nextSeg:0];
  202.  
  203.       if gotLast or (nextID = EndBlockID) or (nextID = MidBlockID) then begin
  204.         blockNum := Succ(blockNum);
  205.         gotFirst := True;
  206.         with Blocks[blockNum] do begin
  207.           mcb := mcbSeg;
  208.           psp := pspAdd;
  209.         end;
  210.       end;
  211.  
  212.     end {Storetheblock} ;
  213.  
  214.   begin
  215.  
  216.     {Initialize}
  217.     StartMCB := GetStartMCB;
  218.     mcbSeg := StartMCB;
  219.     gotFirst := False;
  220.     gotLast := False;
  221.     blockNum := 0;
  222.  
  223.     {Scan all memory until the last block is found}
  224.     repeat
  225.       idbyte := Mem[mcbSeg:0];
  226.       if idbyte = MidBlockID then begin
  227.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  228.         if gotFirst then
  229.           mcbSeg := nextSeg
  230.         else
  231.           mcbSeg := Succ(mcbSeg);
  232.       end else if gotFirst and (idbyte = EndBlockID) then begin
  233.         gotLast := True;
  234.         StoreTheBlock(mcbSeg, nextSeg, gotFirst, gotLast);
  235.       end else
  236.         {Start block was invalid}
  237.         Abort('Corrupted allocation chain or program error....');
  238.     until gotLast;
  239.  
  240.   end {Findtheblocks} ;
  241.  
  242.   function StUpcase(s : AllStrings) : AllStrings;
  243.     {-Return the uppercase string}
  244.   var
  245.     i : Byte;
  246.  
  247.   begin
  248.     for i := 1 to Length(s) do
  249.       s[i] := UpCase(s[i]);
  250.     StUpcase := s;
  251.   end {Stupcase} ;
  252.  
  253.   function FindMark(markName, MarkID : AllStrings;
  254.                     MarkOffset : Integer;
  255.                     var MemMark, FilMark : Boolean;
  256.                     var b : BlockType) : Boolean;
  257.     {-Find the last memory block matching idstring at offset idoffset}
  258.  
  259.     function HasIDstring(segment : Integer;
  260.                          idString : AllStrings;
  261.                          idOffset : Integer) : Boolean;
  262.       {-Return true if idstring is found at segment:idoffset}
  263.     var
  264.       tString : AllStrings;
  265.       len : Byte;
  266.     begin
  267.       len := Length(idString);
  268.       tString[0] := Chr(len);
  269.       Move(Mem[segment:idOffset], tString[1], len);
  270.       HasIDstring := (tString = idString);
  271.     end {HasIDstring} ;
  272.  
  273.     function GetMarkName(segment : Integer) : AllStrings;
  274.       {-Return a cleaned up mark name from the segment's PSP}
  275.     var
  276.       tString : AllStrings;
  277.       tlen : Byte absolute tString;
  278.     begin
  279.       Move(Mem[segment:$80], tString[0], 128);
  280.       while (tlen > 0) and ((tString[1] = ' ') or (tString[1] = ^I)) do
  281.         Delete(tString, 1, 1);
  282.       while (tlen > 0) and ((tString[tlen] = ' ') or (tString[tlen] = ^I)) do
  283.         tlen := Pred(tlen);
  284.       GetMarkName := StUpcase(tString);
  285.     end;                      {GetMarkName}
  286.  
  287.     function MatchMemMark(segment : Integer;
  288.                           markName : AllStrings;
  289.                           var b : BlockType) : Boolean;
  290.       {-Return true if MemMark is unnamed or matches current name}
  291.     var
  292.       tString : AllStrings;
  293.       FoundIt : Boolean;
  294.     begin
  295.       {Check the mark name stored in the PSP of the mark block}
  296.       tString := GetMarkName(segment);
  297.       if (markName <> '') then begin
  298.         FoundIt := (tString = StUpcase(markName));
  299.         if not(FoundIt) then
  300.           if (tString <> '') and (tString[1] = ProtectChar) then
  301.             {Current mark is protected, stop searching}
  302.             b := 1;
  303.       end else if (tString <> '') and (tString[1] = ProtectChar) then begin
  304.         {Stored mark name is protected}
  305.         FoundIt := False;
  306.         {Stop checking}
  307.         b := 1;
  308.       end else
  309.         {Match any mark}
  310.         FoundIt := True;
  311.       if not(FoundIt) then
  312.         b := Pred(b);
  313.       MatchMemMark := FoundIt;
  314.     end {MatchMemMark} ;
  315.  
  316.     function MatchFilMark(segment : Integer;
  317.                           markName : AllStrings;
  318.                           var b : BlockType) : Boolean;
  319.       {-Return true if FilMark is unnamed or matches current name}
  320.     var
  321.       tString : AllStrings;
  322.       FoundIt : Boolean;
  323.  
  324.       function ExistFile(path : AllStrings) : Boolean;
  325.         {-Return true if file exists}
  326.       var
  327.         f : file;
  328.       begin
  329.         Assign(f, path);
  330.         {$I-}
  331.         Reset(f);
  332.         {$I+}
  333.         ExistFile := (IOResult = 0);
  334.         Close(f);
  335.       end;                    {Existfile}
  336.  
  337.     begin
  338.       {Check the mark name stored in the PSP of the mark block}
  339.       tString := GetMarkName(segment);
  340.       if (markName <> '') then begin
  341.         markName := StUpcase(markName);
  342.         FoundIt := (tString = markName);
  343.         if FoundIt then begin
  344.           {Assure named file exists}
  345.           WriteLn('Finding mark file ', markName);
  346.           FoundIt := ExistFile(markName);
  347.           if not(FoundIt) then
  348.             {Stop checking}
  349.             b := 1;
  350.         end;
  351.       end else
  352.         {File marks must be named on RELEASE command line}
  353.         FoundIt := False;
  354.       if not(FoundIt) then
  355.         b := Pred(b);
  356.       MatchFilMark := FoundIt;
  357.     end {MatchFilMark} ;
  358.  
  359.   begin
  360.     {Scan from the last block down to find the last MARK TSR}
  361.     b := blockNum;
  362.     MemMark := False;
  363.     FilMark := False;
  364.     repeat
  365.       if Blocks[b].psp = CSeg then
  366.         {Assure this program's command line is not matched}
  367.         b := Pred(b)
  368.       else if HasIDstring(Blocks[b].psp, MarkID, MarkOffset) then
  369.         {An in-memory mark}
  370.         MemMark := MatchMemMark(Blocks[b].psp, markName, b)
  371.       else if HasIDstring(Blocks[b].psp, FmarkID, FmarkOffset) then
  372.         {A file mark}
  373.         FilMark := MatchFilMark(Blocks[b].psp, markName, b)
  374.       else
  375.         {Not a mark}
  376.         b := Pred(b);
  377.     until (b < 1) or MemMark or FilMark;
  378.     FindMark := MemMark or FilMark;
  379.   end {Findmark} ;
  380.  
  381.   function Hex(i : Integer) : HexString;
  382.     {-Return hex representation of integer}
  383.   const
  384.     hc : array[0..15] of Char = '0123456789ABCDEF';
  385.   var
  386.     l, h : Byte;
  387.   begin
  388.     l := Lo(i);
  389.     h := Hi(i);
  390.     Hex := hc[h shr 4]+hc[h and $F]+hc[l shr 4]+hc[l and $F];
  391.   end {Hex} ;
  392.  
  393.   procedure ReadMarkFile(markName : AllStrings);
  394.     {-Read the mark file info into memory}
  395.   var
  396.     f : file;
  397.   begin
  398.     Assign(f, markName);
  399.     Reset(f, 1);
  400.  
  401.     {Read the vector table from the mark file, into a temporary memory area}
  402.     BlockRead(f, Vectors, 1024);
  403.  
  404.     {Read the BIOS miscellaneous save areas into temporary tables}
  405.     BlockRead(f, EGAsavTable, 8);
  406.     BlockRead(f, IntComTable, 16);
  407.  
  408.     {Read the number of EMS handles stored}
  409.     BlockRead(f, FilMarkHandles, 2);
  410.  
  411.     {Get a page map area and read the page map into it}
  412.     GetMem(FilMarkPageMap, 4*FilMarkHandles);
  413.     BlockRead(f, FilMarkPageMap^, 4*FilMarkHandles);
  414.     Close(f);
  415.  
  416.     if not(KeepMark) then
  417.       {Delete the mark file so it causes no mischief later}
  418.       Erase(f);
  419.   end {ReadMarkFile} ;
  420.  
  421.   procedure CopyVectors(bottomBlock : BlockType);
  422.     {-Put interrupt vectors back into table}
  423.   var
  424.     bottompsp : Integer;
  425.  
  426.     procedure Reset8259;
  427.       {-Reset the 8259 interrupt controller to its powerup state}
  428.       {-Interrupts assumed OFF prior to calling this routine}
  429.  
  430.       function ATmachine : Boolean;
  431.         {-Return true if machine is AT class}
  432.       var
  433.         machtype : Byte absolute $FFFF : $000E;
  434.       begin
  435.         ATmachine := (machtype = $FC);
  436.       end {ATmachine} ;
  437.  
  438.       procedure Reset8259PC;
  439.         {-Reset the 8259 on a PC class machine}
  440.       begin
  441.         inline(
  442.           $E4/$21/            { in      al,$21}
  443.           $88/$C4/            { mov     ah,al}
  444.           $B0/$13/            { mov     al,+$13}
  445.           $E6/$20/            { out     $20,al}
  446.           $B0/$08/            { mov     al,+$08}
  447.           $E6/$21/            { out     $21,al}
  448.           $B0/$09/            { mov     al,+$09}
  449.           $E6/$21/            { out     $21,al}
  450.           $88/$E0/            { mov     al,ah}
  451.           $E6/$21             { out     $21,al}
  452.           );
  453.       end {Reset8259PC} ;
  454.  
  455.       procedure Reset8259AT;
  456.         {-Reset the 8259 interrupt controllers on an AT machine}
  457.       begin
  458.         inline(
  459.           $32/$C0/            { xor       al,al }
  460.           $E6/$F1/            { out       0f1h,al         ; Switch off an 80287 if necessary}
  461.           {Set up master 8259 }
  462.           $E4/$21/            { in        al,21h          ; Get current interrupt mask }
  463.           $8A/$E0/            { mov       ah,al           ; save it }
  464.           $B0/$11/            { mov       al,11h }
  465.           $E6/$20/            { out       20h,al }
  466.           $EB/$00/            { jmp       short $+2 }
  467.           $B0/$08/            { mov       al,8            ; Set up main interrupt vector number}
  468.           $E6/$21/            { out       21h,al }
  469.           $EB/$00/            { jmp       short $+2 }
  470.           $B0/$04/            { mov       al,4 }
  471.           $E6/$21/            { out       21h,al }
  472.           $EB/$00/            { jmp       short $+2 }
  473.           $B0/$01/            { mov       al,1 }
  474.           $E6/$21/            { out       21h,al }
  475.           $EB/$00/            { jmp       short $+2 }
  476.           $8A/$C4/            { mov       al,ah }
  477.           $E6/$21/            { out       21h,al }
  478.           {Set up slave 8259 }
  479.           $E4/$A1/            { in        al,0a1h         ; Get current interrupt mask }
  480.           $8A/$E0/            { mov       ah,al           ; save it }
  481.           $B0/$11/            { mov       al,11h }
  482.           $E6/$A0/            { out       0a0h,al }
  483.           $EB/$00/            { jmp       short $+2 }
  484.           $B0/$70/            { mov       al,70h }
  485.           $E6/$A1/            { out       0a1h,al }
  486.           $B0/$02/            { mov       al,2 }
  487.           $EB/$00/            { jmp       short $+2 }
  488.           $E6/$A1/            { out       0a1h,al }
  489.           $EB/$00/            { jmp       short $+2 }
  490.           $B0/$01/            { mov       al,1 }
  491.           $E6/$A1/            { out       0a1h,al }
  492.           $EB/$00/            { jmp       short $+2 }
  493.           $8A/$C4/            { mov       al,ah           ; Reset previous interrupt state }
  494.           $E6/$A1             { out       0a1h,al }
  495.           );
  496.       end {Reset8259AT} ;
  497.  
  498.     begin
  499.       if ATmachine then
  500.         Reset8259AT
  501.       else
  502.         Reset8259PC;
  503.     end {Reset8259} ;
  504.  
  505.   begin
  506.  
  507.     {Interrupts off}
  508.     inline($FA);
  509.  
  510.     {Reset 8259 if requested}
  511.     if Revector8259 then
  512.       Reset8259;
  513.  
  514.     {Restore the main interrupt vector table and the misc save areas}
  515.     if FilMark then begin
  516.       Move(Vectors, Mem[0:0], 1024);
  517.       Move(EGAsavTable, Mem[$40:$A8], 8);
  518.       Move(IntComTable, Mem[$40:$F0], 16);
  519.     end else begin
  520.       bottompsp := Blocks[bottomBlock].psp;
  521.       Move(Mem[bottompsp:VectorOffset], Mem[0:0], 1024);
  522.       Move(Mem[bottompsp:EGAsavOffset], Mem[$40:$A8], 8);
  523.       Move(Mem[bottompsp:IntComOffset], Mem[$40:$F0], 16);
  524.     end;
  525.  
  526.     {Interrupts on}
  527.     inline($FB);
  528.  
  529.     {Move the old termination/break/error addresses into this program}
  530.     Move(Mem[0:$88], Mem[CSeg:$0A], 12);
  531.  
  532.     {Restore the "parent address" used by the DOS EXIT command to remove a shell}
  533.     Move(Mem[CSeg:$0C], Mem[CSeg:$16], 2);
  534.  
  535.   end {CopyVectors} ;
  536.  
  537.   procedure MarkBlocks(bottomBlock : BlockType);
  538.     {-Mark those blocks to be released}
  539.   var
  540.     b : BlockType;
  541.     commandPsp, markPsp : Integer;
  542.     ch : Char;
  543.  
  544.     procedure BatchWarning(b : BlockType);
  545.       {-Warn about the trapping effect of batch files}
  546.     var
  547.       t : BlockType;
  548.  
  549.       function Cardinal(i : Integer) : Real;
  550.         {-Return unsigned integer 0..65535 in a real}
  551.       begin
  552.         if i < 0 then
  553.           Cardinal := 65536.0+i
  554.         else
  555.           Cardinal := i;
  556.       end {Cardinal} ;
  557.  
  558.     begin
  559.       WriteLn('Memory space for TSRs installed prior to batch file');
  560.       WriteLn('will not be released until batch file completes.');
  561.       WriteLn;
  562.       ReturnCode := 1;
  563.       {Accumulate number of bytes temporarily trapped}
  564.       for t := 1 to b do
  565.         if Blocks[t].releaseIt then
  566.           TrappedBytes := TrappedBytes+16.0*Cardinal(MemW[Blocks[t].mcb:3]);
  567.     end {BatchWarning} ;
  568.  
  569.   begin
  570.  
  571.     commandPsp := Blocks[2].psp;
  572.     markPsp := Blocks[bottomBlock].psp;
  573.  
  574.     for b := 1 to blockNum do
  575.       with Blocks[b] do
  576.         if (b < bottomBlock) then begin
  577.           {Release any trapped environment block}
  578.           if KeepMark then
  579.             releaseIt := (psp <> CSeg) and (psp xor $8000 > markPsp xor $8000)
  580.           else
  581.             releaseIt := (psp <> CSeg) and (psp xor $8000 >= markPsp xor $8000);
  582.         end else if (psp = commandPsp) then begin
  583.           {Don't release blocks owned by COMMAND.COM}
  584.           releaseIt := False;
  585.           BatchWarning(b);
  586.         end else if KeepMark then
  587.           {Release all but RELEASE and the mark}
  588.           releaseIt := (psp <> CSeg) and (psp <> markPsp)
  589.         else
  590.           {Release all but RELEASE itself}
  591.           releaseIt := (psp <> CSeg);
  592.  
  593.     if Debug then begin
  594.       for b := 1 to blockNum do with Blocks[b] do
  595.         WriteLn(b:3, ' ', Hex(psp), ' ', Hex(mcb), ' ', releaseIt);
  596.       Read(Kbd, ch);
  597.     end;
  598.  
  599.   end {MarkBlocks} ;
  600.  
  601.   procedure ReleaseMem;
  602.     {-Release DOS memory marked for release}
  603.   var
  604.     b : BlockType;
  605.   begin
  606.     with Regs do
  607.       for b := 1 to blockNum do
  608.         with Blocks[b] do
  609.           if releaseIt then begin
  610.             ah := $49;
  611.             {The block is always 1 paragraph above the MCB}
  612.             es := Succ(mcb);
  613.             MsDos(Regs);
  614.             if Odd(flags) then begin
  615.               WriteLn('Could not release block at segment ', Hex(es));
  616.               Abort('Memory may be a mess... Please reboot');
  617.             end;
  618.           end;
  619.   end {Releasemem} ;
  620.  
  621.   procedure UpdateWatch(watchBlock : BlockType);
  622.     {-Write a new watch data area based on the release and the original watch}
  623.   type
  624.     ChangeBlock =
  625.     record
  626.       VecID : Integer;
  627.       VecOfs : Integer;
  628.       VecSeg : Integer;
  629.       PatchWord : Integer;
  630.     end;
  631.   var
  632.     changes : array[0..MaxChanges] of ChangeBlock;
  633.     p : ^ChangeBlock;
  634.     watchseg, c, o, i, actualmax : Integer;
  635.     KeepPSP : Boolean;
  636.  
  637.     function WillKeepPSP(pspAdd : Integer) : Boolean;
  638.       {-Return true if this psp address will be kept}
  639.     var
  640.       b : BlockType;
  641.     begin
  642.       for b := 1 to blockNum do
  643.         with Blocks[b] do
  644.           if psp = pspAdd then begin
  645.             WillKeepPSP := not(releaseIt);
  646.             Exit;
  647.           end;
  648.     end {WillKeepPSP} ;
  649.  
  650.   begin
  651.  
  652.     {Initialize}
  653.     watchseg := Blocks[watchBlock].psp;
  654.     actualmax := MemW[watchseg:NextChange];
  655.  
  656.     {Transfer changes from WATCH into a buffer array}
  657.     i := 0;
  658.     o := 0;
  659.     while i < actualmax do begin
  660.       p := Ptr(watchseg, ChangeVectors+i);
  661.       Move(p^, changes[o], SizeOf(ChangeBlock));
  662.       i := i+SizeOf(ChangeBlock);
  663.       o := Succ(o);
  664.     end;
  665.  
  666.     {Determine which change records to keep and transfer them back to WATCH}
  667.     KeepPSP := True;
  668.     i := 0;
  669.     for c := 0 to Pred(o) do begin
  670.       with changes[c] do
  671.         if VecID = $FFFF then
  672.           {This record starts a new PSP. See if PSP is kept in memory}
  673.           KeepPSP := WillKeepPSP(VecOfs);
  674.       if KeepPSP then begin
  675.         p := Ptr(watchseg, ChangeVectors+i);
  676.         Move(changes[c], p^, SizeOf(ChangeBlock));
  677.         i := i+SizeOf(ChangeBlock);
  678.       end;
  679.     end;
  680.     MemW[watchseg:NextChange] := i;
  681.  
  682.     {Update the WATCH image of the vector table to whatever's current}
  683.     Move(Mem[0:0], Mem[watchseg:CurrVectors], 1024);
  684.  
  685.   end {UpdateWatch} ;
  686.  
  687.   function EMSpresent : Boolean;
  688.     {-Return true if EMS memory manager is present}
  689.   var
  690.     f : file;
  691.   begin
  692.     {"file handle" defined by the expanded memory manager at installation}
  693.     Assign(f, 'EMMXXXX0');
  694.     {$I-}
  695.     Reset(f);
  696.     {$I+}
  697.     EMSpresent := (IOResult = 0);
  698.     Close(f);
  699.   end {EMSpresent} ;
  700.  
  701.   procedure RestoreEMSmap;
  702.     {-Restore EMS to state at time of mark}
  703.  
  704.     function GetHandles(bottomBlock : BlockType; EMScntOffset : Integer) : Integer;
  705.       {-Return the number of handles stored by mark}
  706.     begin
  707.       if FilMark then
  708.         GetHandles := FilMarkHandles
  709.       else
  710.         GetHandles := MemW[Blocks[bottomBlock].psp:EMScntOffset];
  711.     end {Gethandles} ;
  712.  
  713.     function GetStoredMap(bottomBlock : BlockType; EMSmapOffset : Integer) : PageArrayPtr;
  714.       {-Returns a pointer to the stored page array}
  715.     begin
  716.       if FilMark then
  717.         GetStoredMap := FilMarkPageMap
  718.       else
  719.         GetStoredMap := Ptr(Blocks[bottomBlock].psp, EMSmapOffset);
  720.     end {GetStoredMap} ;
  721.  
  722.     procedure EMSpageMap(var PageMap : PageArray; var EMShandles:integer);
  723.       {-return an array of the allocated memory blocks}
  724.     begin
  725.       regs.ah := $4D;
  726.       regs.es := Seg(PageMap);
  727.       regs.di := Ofs(PageMap);
  728.       regs.bx := 0;
  729.       Intr(EMSinterrupt, regs);
  730.       if regs.ah <> 0 then begin
  731.         WriteLn('EMS device not responding');
  732.         emshandles:=0;
  733.       end else
  734.         emshandles:=regs.bx;
  735.     end {EMSpageMap} ;
  736.  
  737.     procedure ReleaseEMSblocks(var oldmap, newmap : PageArray);
  738.       {-Release those EMS blocks allocated since MARK was installed}
  739.     var
  740.       o, n, nhandle : Integer;
  741.  
  742.       procedure EMSdeallocate(EMShandle : Integer);
  743.         {-Release the allocated expanded memory}
  744.       begin
  745.         Regs.ah := $45;
  746.         Regs.dx := EMShandle;
  747.         Intr(EMSinterrupt, Regs);
  748.         if Regs.ah <> 0 then begin
  749.           WriteLn('Program error or EMS device not responding');
  750.           Abort('EMS memory may be a mess... Please reboot');
  751.         end;
  752.       end {EMSdeallocate} ;
  753.  
  754.     begin
  755.       for n := 1 to EMShandles do begin
  756.         {Scan all current handles}
  757.         nhandle := newmap[n].handle;
  758.         if StoredHandles > 0 then begin
  759.           {See if current handle matches one stored by MARK}
  760.           o := 1;
  761.           while (oldmap[o].handle <> nhandle) and (o <= StoredHandles) do
  762.             o := Succ(o);
  763.           {If not, deallocate the current handle}
  764.           if (o > StoredHandles) then
  765.             EMSdeallocate(nhandle);
  766.         end else
  767.           {No handles stored by MARK, deallocate all current handles}
  768.           EMSdeallocate(nhandle);
  769.       end;
  770.     end {ReleaseEMSblocks} ;
  771.  
  772.   begin
  773.     {Get the existing EMS page map}
  774.     GetMem(Map, 2048);
  775.     EMSpageMap(Map^, EMShandles);
  776.     if EMShandles > MaxHandles then
  777.       WriteLn('EMS process count exceeds capacity of RELEASE - no action taken')
  778.     else if EMShandles <> 0 then begin
  779.       {See how many handles were active when MARK was installed}
  780.       StoredHandles := GetHandles(bottomBlock, EMScntOffset);
  781.       {Get the stored page map}
  782.       StoredMap := GetStoredMap(bottomBlock, EMSmapOffset);
  783.       {Compare the two maps and deallocate pages not in the stored map}
  784.       ReleaseEMSblocks(StoredMap^, Map^);
  785.     end;
  786.   end {RestoreEMSmap} ;
  787.  
  788.   procedure GetOptions;
  789.     {-Analyze command line for options}
  790.   var
  791.     arg : AllStrings;
  792.     arglen : Byte absolute arg;
  793.     i : Integer;
  794.  
  795.     procedure WriteHelp;
  796.       {-Show the options}
  797.     begin
  798.       WriteLn('RELEASE ', Version, ', by TurboPower Software');
  799.       WriteLn('====================================================');
  800.       WriteLn('RELEASE removes memory-resident programs from memory');
  801.       WriteLn('and restores the interrupt vectors to their state as');
  802.       WriteLn('found prior to the installation of a MARK.');
  803.       WriteLn('RELEASE manages both normal DOS memory and also');
  804.       WriteLn('Lotus/Intel Expanded Memory. If WATCH has been installed,');
  805.       WriteLn('RELEASE will update the WATCH data area for the TSRs');
  806.       WriteLn('released.');
  807.       WriteLn;
  808.       WriteLn('RELEASE accepts the following command line syntax:');
  809.       WriteLn;
  810.       WriteLn('  RELEASE [MarkName] [Options]');
  811.       WriteLn;
  812.       WriteLn('Options may be preceded by either / or -. Valid options');
  813.       WriteLn('are as follows:');
  814.       WriteLn;
  815.       WriteLn('     /K     release memory, but Keep the mark in place.');
  816.       writeln('     /N     do Not touch EMS memory in any way.');
  817.       WriteLn('     /R     Revector the 8259 interrupt controller to its');
  818.       WriteLn('            powerup state.');
  819.       WriteLn('     /?     write this help screen.');
  820.       Halt(1);
  821.     end {WriteHelp} ;
  822.  
  823.   begin
  824.  
  825.     WriteLn;
  826.  
  827.     {Initialize defaults}
  828.     markName := '';
  829.     Revector8259 := False;
  830.     KeepMark := False;
  831.     DealWithEMS := True;
  832.     ReturnCode := 0;
  833.     TrappedBytes := 0.0;
  834.     Debug := False;
  835.  
  836.     i := 1;
  837.     while i <= ParamCount do begin
  838.       arg := ParamStr(i);
  839.       if (arg[1] = '?') then
  840.         WriteHelp
  841.       else if (arg[1] = '-') or (arg[1] = '/') then
  842.         case arglen of
  843.           1 : Abort('Missing command option following '+arg);
  844.           2 : case UpCase(arg[2]) of
  845.                 '?' : WriteHelp;
  846.                 'R' : Revector8259 := True;
  847.                 'K' : KeepMark := True;
  848.                 'N' : DealWithEMS := False;
  849.                 'D' : Debug := True;
  850.               else
  851.                 Abort('Unknown command option: '+arg);
  852.               end;
  853.         else
  854.           Abort('Unknown command option: '+arg);
  855.         end
  856.       else
  857.         {Named mark}
  858.         markName := arg;
  859.       i := Succ(i);
  860.     end;
  861.  
  862.   end {GetOptions} ;
  863.  
  864. begin
  865.  
  866.   {Analyze command line for options}
  867.   GetOptions;
  868.  
  869.   {Get all allocated memory blocks in normal memory}
  870.   FindTheBlocks;
  871.  
  872.   {Find the last one marked with the MARK idstring, and MarkName if specified}
  873.   if not(FindMark(markName, MarkID, MarkOffset, MemMark, FilMark, bottomBlock)) then
  874.     Abort('No matching marker found, or protected marker encountered.');
  875.  
  876.   {Find the watch block, if any}
  877.   UseWatch := FindMark('', WatchID, WatchOffset, Junk, Junk, watchBlock);
  878.  
  879.   {Mark those blocks to be released}
  880.   MarkBlocks(bottomBlock);
  881.  
  882.   {Get file mark information into memory}
  883.   if FilMark then
  884.     ReadMarkFile(markName);
  885.  
  886.   {Copy the vector table from the MARK copy}
  887.   CopyVectors(bottomBlock);
  888.  
  889.   {Update the watch block if requested}
  890.   if UseWatch then
  891.     {The WATCH ID was found in memory}
  892.     if not(Blocks[watchBlock].releaseIt) then
  893.       {Watch itself won't be released}
  894.       UpdateWatch(watchBlock);
  895.  
  896.   {Release normal memory marked for release}
  897.   ReleaseMem;
  898.  
  899.   {Deal with expanded memory}
  900.   if DealWithEMS then
  901.     if EMSpresent then
  902.       RestoreEMSmap;
  903.  
  904.   {Write success message}
  905.   Write('RELEASE ', Version, ' - Memory released above last MARK ');
  906.   if markName <> '' then
  907.     Write('(', StUpcase(markName), ')');
  908.   WriteLn;
  909.  
  910.   if ReturnCode <> 0 then
  911.     WriteLn(TrappedBytes:0:0, ' bytes temporarily trapped until batch file completes');
  912.  
  913.   Halt(ReturnCode);
  914. end.
  915.